/*
* Copyright (c) 2007 by Kirill Kolodyazhniy.
* See the file "license.terms" for information on usage and redistribution.
*/

#ifndef M_AABB_H
#define M_AABB_H

#include "common.h"
#include "matrix4.h"
#include "vector3.h"

namespace Engine
{
	/**
	*   Axis aligned bounding box.
	*
	* 1-----2
	* /|    /|
	* / |   / |
	* 5-----4  |
	* |  0--|--3
	* | /   | /
	* |/    |/
	* 6-----7
	*/
	class AABB
	{
	public:

		/**
		*   Default constructor.
		*/
		AABB(){}

		/**
		*   Copy constructor.
		*/
		AABB(const AABB& bbox)
		{
			SetExtents(bbox.m_min,bbox.m_max);
		}

		/**
		*   Constructor.
		*/
		AABB(const Vector3<REAL>& min,const Vector3<REAL>& max)
		{
			SetExtents(min,max);
		}

		/**
		*   Gets the minimum corner of the box.
		*/
		inline const Vector3<REAL>& GetMinimum(void) const
		{
			return m_min;
		}

		/**
		*   Gets a modifiable version of the minimum corner of the box.
		*/
		inline Vector3<REAL>& GetMinimum(void)
		{
			return m_min;
		}

		/**
		*   Gets the maximum corner of the box.
		*/
		inline const Vector3<REAL>& GetMaximum(void) const
		{
			return m_max;
		}

		/**
		*   Gets a modifiable version of the maximum corner of the box.
		*/
		inline Vector3<REAL>& GetMaximum(void)
		{
			return m_max;
		}

		/**
		*   Return
		*/
		Vector3<REAL> GetVertex(unsigned int index)const
		{
			switch (index)
			{
				case 0:
					return m_min;
				case 1:
					return Vector3<REAL>(m_min.x, m_max.y, m_min.z);
				case 2:
					return Vector3<REAL>(m_max.x, m_max.y, m_min.z);
				case 3:
					return Vector3<REAL>(m_max.x, m_min.y, m_min.z);
				case 4:
					return Vector3<REAL>(m_max.x, m_min.y, m_max.z);
				case 5:
					return Vector3<REAL>(m_min.x, m_min.y, m_max.z);
				case 6:
					return Vector3<REAL>(m_min.x, m_max.y, m_max.z);
				case 7:
					return m_max;
				default:
					return Vector3<REAL>();
			}
		}

		/**
		*   Merges the passed in box into the current box. The result is the
		*   box which encompasses both.
		*/
		void Merge( const AABB& bbox )
		{
			if(!m_min.IsZeroLength() || !m_max.IsZeroLength ())
			{
				Vector3<REAL> min = m_min;
				Vector3<REAL> max = m_max;
				max.MakeCeil(bbox.m_max);
				min.MakeFloor(bbox.m_min);

				SetExtents(min, max);
			}
			else
			{
				SetExtents(bbox.m_min, bbox.m_max);
			}
		}

		/**
		*   Extends the box to encompass the specified point.
		*/
		void Merge( const Vector3<REAL>& point )
		{
			if(!m_max.IsZeroLength() || !m_min.IsZeroLength())
			{
				m_max.MakeCeil(point);
				m_min.MakeFloor(point);
			}
			else
			{
				m_max = m_min = point;
			}
		}

		/**
		*   Set extents.
		*/
		void SetExtents(const Vector3<REAL>& min,const Vector3<REAL>& max)
		{
			m_min = min;
			m_max = max;
		}

		/**
		*  Gets the center of the box.
		*/
		Vector3<REAL> GetCenter(void) const
		{
			return Vector3<REAL>(
				(m_max.x + m_min.x) * 0.5,
				(m_max.y + m_min.y) * 0.5,
				(m_max.z + m_min.z) * 0.5);
		}

		/**
		*   Gets the size of the box
		*/
		Vector3<REAL> GetSize(void) const
		{
			return m_max - m_min;
		}

		/**
		*   Gets the half size of the box
		*/
		Vector3<REAL> GetHalfSize(void) const
		{
			return (m_max - m_min) * 0.5;
		}

		/**
		*   Transforms the box according to the matrix supplied.
		*/
		inline void Transform( const Matrix4<REAL>& matrix )
		{

			//oobb
			Vector3<REAL> center = GetCenter();
			Vector3<REAL> halfSize = GetHalfSize();

			Vector3<REAL> newCenter = matrix * center;
			Vector3<REAL> newHalfSize(
				fabs(matrix[0][0]) * halfSize.x + fabs(matrix[0][1]) * halfSize.y + fabs(matrix[0][2]) * halfSize.z,
				fabs(matrix[1][0]) * halfSize.x + fabs(matrix[1][1]) * halfSize.y + fabs(matrix[1][2]) * halfSize.z,
				fabs(matrix[2][0]) * halfSize.x + fabs(matrix[2][1]) * halfSize.y + fabs(matrix[2][2]) * halfSize.z);

			SetExtents(newCenter - newHalfSize, newCenter + newHalfSize);
/*  aabb

			Vector3<REAL> oldMin, oldMax, currentCorner;

			// Getting the old values so that we can use the existing merge method.
			oldMin = m_min;
			oldMax = m_max;

			// reset
			Reset();

			// We sequentially compute the corners in the following order :
			// 0, 6, 5, 1, 2, 4 ,7 , 3
			// This sequence allows us to only change one member at a time to get at all corners.

			// For each one, we transform it using the matrix
			// Which gives the resulting point and merge the resulting point.

			// First corner
			// min min min
			currentCorner = oldMin;
			Merge( matrix * currentCorner );

			// min,min,max
			currentCorner.z = oldMax.z;
			Merge( matrix * currentCorner );

			// min max max
			currentCorner.y = oldMax.y;
			Merge( matrix * currentCorner );

			// min max min
			currentCorner.z = oldMin.z;
			Merge( matrix * currentCorner );

			// max max min
			currentCorner.x = oldMax.x;
			Merge( matrix * currentCorner );

			// max max max
			currentCorner.z = oldMax.z;
			Merge( matrix * currentCorner );

			// max min max
			currentCorner.y = oldMin.y;
			Merge( matrix * currentCorner );

			// max min min
			currentCorner.z = oldMin.z;
			Merge( matrix * currentCorner );
*/
		}

		/**
		*   Reset box to null size.
		*/
		void Reset()
		{
			m_min = Vector3<REAL>::ZERO;
			m_max = Vector3<REAL>::ZERO;
		}

		/**
		*   Return true if box is empty
		*/
		bool IsNull() const
		{
			if(m_min == Vector3<REAL>::ZERO && m_max == Vector3<REAL>::ZERO)
				return true;
			return false;
		};
	private:
		Vector3<REAL> m_min;
		Vector3<REAL> m_max;
	};
}

#endif
